home *** CD-ROM | disk | FTP | other *** search
- ; Fixed point routines.
- ; Tested with TASM 3.0.
-
- USE386 equ 1 ;1 for 386-specific opcodes, 0 for
- ; 8088 opcodes
- MUL_ROUNDING_ON equ 0 ;1 for rounding on multiplies,
- ; 0 for no rounding. Not rounding is faster,
- ; rounding is more accurate and generally a
- ; good idea
- DIV_ROUNDING_ON equ 0 ;1 for rounding on divides,
- ; 0 for no rounding. Not rounding is faster,
- ; rounding is more accurate, but because
- ; division is only performed to project to
- ; the screen, rounding quotients generally
- ; isn't necessary
- ALIGNMENT equ 2
-
- .model large
- .386
- .code
-
- ;=====================================================================
- ; Multiplies two fixed-point values together.
- ; C near-callable as:
- ; Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2);
- FMparms struc
- dd
- dw ? ;return address & pushed BP
-
- M1 dd ?
- M2 dd ?
- FMparms ends
- align ALIGNMENT
- public _FixedMul
- _FixedMul proc far
- push bp
- mov bp,sp
-
- if USE386
-
- mov eax,[bp+M1]
- imul dword ptr [bp+M2] ;multiply
- if MUL_ROUNDING_ON
- add eax,8000h ;round by adding 2^(-17)
- adc edx,0 ;whole part of result is in DX
- endif ;MUL_ROUNDING_ON
- shr eax,16 ;put the fractional part in AX
-
- else ;!USE386
-
- ;do four partial products and
- ; add them together, accumulating
- ; the result in CX:BX
- push si ;preserve C register variables
- push di
- ;figure out signs, so we can use
- ; unsigned multiplies
- sub cx,cx ;assume both operands positive
- mov ax,word ptr [bp+M1+2]
- mov si,word ptr [bp+M1]
- and ax,ax ;first operand negative?
- jns CheckSecondOperand ;no
- neg ax ;yes, so negate first operand
- neg si
- sbb ax,0
- inc cx ;mark that first operand is negative
- CheckSecondOperand:
- mov bx,word ptr [bp+M2+2]
- mov di,word ptr [bp+M2]
- and bx,bx ;second operand negative?
- jns SaveSignStatus ;no
- neg bx ;yes, so negate second operand
- neg di
- sbb bx,0
- xor cx,1 ;mark that second operand is negative
- SaveSignStatus:
- push cx ;remember sign of result; 1 if result
- ; negative, 0 if result nonnegative
- push ax ;remember high word of M1
- mul bx ;high word M1 times high word M2
- mov cx,ax ;accumulate result in CX:BX (BX not used
- ; until next operation, however)
- ;assume no overflow into DX
- mov ax,si ;low word M1 times high word M2
- mul bx
- mov bx,ax
- add cx,dx ;accumulate result in CX:BX
- pop ax ;retrieve high word of M1
- mul di ;high word M1 times low word M2
- add bx,ax
- adc cx,dx ;accumulate result in CX:BX
- mov ax,si ;low word M1 times low word M2
- mul di
- if MUL_ROUNDING_ON
- add ax,8000h ;round by adding 2^(-17)
- adc bx,dx
- else ;!MUL_ROUNDING_ON
- add bx,dx ;don't round
- endif ;MUL_ROUNDING_ON
- adc cx,0 ;accumulate result in CX:BX
- mov dx,cx
- mov ax,bx
- pop cx
- and cx,cx ;is the result negative?
- jz FixedMulDone ;no, we're all set
- neg dx ;yes, so negate DX:AX
- neg ax
- sbb dx,0
- FixedMulDone:
-
- pop di ;restore C register variables
- pop si
-
- endif ;USE386
-
- pop bp
- ret
- _FixedMul endp
-
- ;=====================================================================
- ; Divides one fixed-point value by another.
- ; C near-callable as:
- ; Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor);
- FDparms struc
- dd
- dw ? ;return address & pushed BP
- Dividend dd ?
- Divisor dd ?
- FDparms ends
- align ALIGNMENT
- public _FixedDiv
- _FixedDiv proc far
- push bp
- mov bp,sp
-
- if USE386
-
- if DIV_ROUNDING_ON
- sub cx,cx ;assume positive result
- mov eax,[bp+Dividend]
- and eax,eax ;positive dividend?
- jns FDP1 ;yes
- inc cx ;mark it's a negative dividend
- neg eax ;make the dividend positive
- FDP1: sub edx,edx ;make it a 64-bit dividend, then shift
- ; left 16 bits so that result will be
- ; in EAX
- rol eax,16 ;put fractional part of dividend in
- ; high word of EAX
- mov dx,ax ;put whole part of dividend in DX
- sub ax,ax ;clear low word of EAX
- mov ebx,dword ptr [bp+Divisor]
- and ebx,ebx ;positive divisor?
- jns FDP2 ;yes
- dec cx ;mark it's a negative divisor
- neg ebx ;make divisor positive
- FDP2: div ebx ;divide
- shr ebx,1 ;divisor/2, minus 1 if the divisor is
- adc ebx,0 ; even
- dec ebx
- cmp ebx,edx ;set Carry if the remainder is at least
- adc eax,0 ; half as large as the divisor, then
- ; use that to round up if necessary
- and cx,cx ;should the result be made negative?
- jz FDP3 ;no
- neg eax ;yes, negate it
- FDP3:
- else ;!DIV_ROUNDING_ON
- mov edx,[bp+Dividend]
- sub eax,eax
- shrd eax,edx,16 ;position so that result ends up
- sar edx,16 ; in EAX
- idiv dword ptr [bp+Divisor]
- endif ;DIV_ROUNDING_ON
- shld edx,eax,16 ;whole part of result in DX;
- ; fractional part is already in AX
-
- else ;!USE386
-
- ;NOTE!!! Non-386 division uses a 32-bit dividend but only the upper 16 bits
- ; of the divisor; in other words, only the integer part of the divisor is
- ; used. This is done so that the division can be accomplished with two fast
- ; hardware divides instead of a slow software implementation, and is (in my
- ; opinion) acceptable because division is only used to project points to the
- ; screen (normally, the divisor is a Z coordinate), so there's no cumulative
- ; error, although there will be some error in pixel placement (the magnitude
- ; of the error is less the farther away from the Z=0 plane objects are). This
- ; is *not* a general-purpose divide, though; if the divisor is less than 1,
- ; for instance, a divide-by-zero error will result! For this reason, non-386
- ; projection can't be performed for points closer to the viewpoint than Z=1.
-
- ;figure out signs, so we can use
- ; unsigned divisions
- sub cx,cx ;assume both operands positive
- mov ax,word ptr [bp+Dividend+2]
- and ax,ax ;first operand negative?
- jns CheckSecondOperandD ;no
- neg ax ;yes, so negate first operand
- neg word ptr [bp+Dividend]
- sbb ax,0
- inc cx ;mark that first operand is negative
- CheckSecondOperandD:
- mov bx,word ptr [bp+Divisor+2]
- and bx,bx ;second operand negative?
- jns SaveSignStatusD ;no
- neg bx ;yes, so negate second operand
- neg word ptr [bp+Divisor]
- sbb bx,0
- xor cx,1 ;mark that second operand is negative
- SaveSignStatusD:
- push cx ;remember sign of result; 1 if result
- ; negative, 0 if result nonnegative
- sub dx,dx ;put Dividend+2 (integer part) in DX:AX
- div bx ;first half of 32/16 division, integer part
- ; divided by integer part
- mov cx,ax ;set aside integer part of result
- mov ax,word ptr [bp+Dividend] ;concatenate the fractional part of
- ; the dividend to the remainder (fractional
- ; part) of the result from dividing the
- ; integer part of the dividend
- div bx ;second half of 32/16 division
-
- if DIV_ROUNDING_ON EQ 0
- shr bx,1 ;divisor/2, minus 1 if the divisor is
- adc bx,0 ; even
- dec bx
- cmp bx,dx ;set Carry if the remainder is at least
- adc ax,0 ; half as large as the divisor, then
- adc cx,0 ; use that to round up if necessary
- endif ;DIV_ROUNDING_ON
-
- mov dx,cx ;absolute value of result in DX:AX
- pop cx
- and cx,cx ;is the result negative?
- jz FixedDivDone ;no, we're all set
- neg dx ;yes, so negate DX:AX
- neg ax
- sbb dx,0
- FixedDivDone:
-
- endif ;USE386
-
- pop bp
- ret
- _FixedDiv endp
-
- end
-